/*
    m68kmmu.h - PMMU implementation for 68851/68030/68040

    By R. Belmont

    Copyright Nicola Salmoria and the MAME Team.
    Visit http://mamedev.org for licensing and usage restrictions.
*/

/*
    pmmu_translate_addr: perform 68851/68030-style PMMU address translation
*/
uint pmmu_translate_addr(uint addr_in)
{
    uint32 addr_out, tbl_entry = 0, tbl_entry2, tamode = 0, tbmode = 0, tcmode = 0;
    uint root_aptr, root_limit, tofs, is, abits, bbits, cbits;
    uint resolved, tptr, shift;

    resolved = 0;
    addr_out = addr_in;

    // if SRP is enabled and we're in supervisor mode, use it
    if ((m68ki_cpu.mmu_tc & 0x02000000) && (m68ki_get_sr() & 0x2000))
    {
        root_aptr = m68ki_cpu.mmu_srp_aptr;
        root_limit = m68ki_cpu.mmu_srp_limit;
    }
    else    // else use the CRP
    {
        root_aptr = m68ki_cpu.mmu_crp_aptr;
        root_limit = m68ki_cpu.mmu_crp_limit;
    }

    // get initial shift (# of top bits to ignore)
    is = (m68ki_cpu.mmu_tc>>16) & 0xf;
    abits = (m68ki_cpu.mmu_tc>>12)&0xf;
    bbits = (m68ki_cpu.mmu_tc>>8)&0xf;
    cbits = (m68ki_cpu.mmu_tc>>4)&0xf;

//  fprintf(stderr,"PMMU: tcr %08x limit %08x aptr %08x is %x abits %d bbits %d cbits %d\n", m68ki_cpu.mmu_tc, root_limit, root_aptr, is, abits, bbits, cbits);

    // get table A offset
    tofs = (addr_in<<is)>>(32-abits);

    // find out what format table A is
    switch (root_limit & 3)
    {
        case 0: // invalid, should cause MMU exception
        case 1: // page descriptor, should cause direct mapping
            fatalerror("680x0 PMMU: Unhandled root mode\n");
            break;

        case 2: // valid 4 byte descriptors
            tofs *= 4;
//          fprintf(stderr,"PMMU: reading table A entry at %08x\n", tofs + (root_aptr & 0xfffffffc));
            tbl_entry = m68k_read_memory_32( tofs + (root_aptr & 0xfffffffc));
            tamode = tbl_entry & 3;
//          fprintf(stderr,"PMMU: addr %08x entry %08x mode %x tofs %x\n", addr_in, tbl_entry, tamode, tofs);
            break;

        case 3: // valid 8 byte descriptors
            tofs *= 8;
//          fprintf(stderr,"PMMU: reading table A entries at %08x\n", tofs + (root_aptr & 0xfffffffc));
            tbl_entry2 = m68k_read_memory_32( tofs + (root_aptr & 0xfffffffc));
            tbl_entry = m68k_read_memory_32( tofs + (root_aptr & 0xfffffffc)+4);
            tamode = tbl_entry2 & 3;
//          fprintf(stderr,"PMMU: addr %08x entry %08x entry2 %08x mode %x tofs %x\n", addr_in, tbl_entry, tbl_entry2, tamode, tofs);
            break;
    }

    // get table B offset and pointer
    tofs = (addr_in<<(is+abits))>>(32-bbits);
    tptr = tbl_entry & 0xfffffff0;

    // find out what format table B is, if any
    switch (tamode)
    {
        case 0: // invalid, should cause MMU exception
            fatalerror("680x0 PMMU: Unhandled Table A mode %d (addr_in %08x)\n", tamode, addr_in);
            break;

        case 2: // 4-byte table B descriptor
            tofs *= 4;
//          fprintf(stderr,"PMMU: reading table B entry at %08x\n", tofs + tptr);
            tbl_entry = m68k_read_memory_32( tofs + tptr);
            tbmode = tbl_entry & 3;
//          fprintf(stderr,"PMMU: addr %08x entry %08x mode %x tofs %x\n", addr_in, tbl_entry, tbmode, tofs);
            break;

        case 3: // 8-byte table B descriptor
            tofs *= 8;
//          fprintf(stderr,"PMMU: reading table B entries at %08x\n", tofs + tptr);
            tbl_entry2 = m68k_read_memory_32( tofs + tptr);
            tbl_entry = m68k_read_memory_32( tofs + tptr + 4);
            tbmode = tbl_entry2 & 3;
//          fprintf(stderr,"PMMU: addr %08x entry %08x entry2 %08x mode %x tofs %x\n", addr_in, tbl_entry, tbl_entry2, tbmode, tofs);
            break;

        case 1: // early termination descriptor
            tbl_entry &= 0xffffff00;

            shift = is+abits;
            addr_out = ((addr_in<<shift)>>shift) + tbl_entry;
            resolved = 1;
            break;
    }

    // if table A wasn't early-out, continue to process table B
    if (!resolved)
    {
        // get table C offset and pointer
        tofs = (addr_in<<(is+abits+bbits))>>(32-cbits);
        tptr = tbl_entry & 0xfffffff0;

        switch (tbmode)
        {
            case 0: // invalid, should cause MMU exception
                fatalerror("680x0 PMMU: Unhandled Table B mode %d (addr_in %08x PC %x)\n", tbmode, addr_in, REG_PC);
                break;

            case 2: // 4-byte table C descriptor
                tofs *= 4;
//              fprintf(stderr,"PMMU: reading table C entry at %08x\n", tofs + tptr);
                tbl_entry = m68k_read_memory_32(tofs + tptr);
                tcmode = tbl_entry & 3;
//              fprintf(stderr,"PMMU: addr %08x entry %08x mode %x tofs %x\n", addr_in, tbl_entry, tbmode, tofs);
                break;

            case 3: // 8-byte table C descriptor
                tofs *= 8;
//              fprintf(stderr,"PMMU: reading table C entries at %08x\n", tofs + tptr);
                tbl_entry2 = m68k_read_memory_32(tofs + tptr);
                tbl_entry = m68k_read_memory_32(tofs + tptr + 4);
                tcmode = tbl_entry2 & 3;
//              fprintf(stderr,"PMMU: addr %08x entry %08x entry2 %08x mode %x tofs %x\n", addr_in, tbl_entry, tbl_entry2, tbmode, tofs);
                break;

            case 1: // termination descriptor
                tbl_entry &= 0xffffff00;

                shift = is+abits+bbits;
                addr_out = ((addr_in<<shift)>>shift) + tbl_entry;
                resolved = 1;
                break;
        }
    }

    if (!resolved)
    {
        switch (tcmode)
        {
            case 0: // invalid, should cause MMU exception
            case 2: // 4-byte ??? descriptor
            case 3: // 8-byte ??? descriptor
                fatalerror("680x0 PMMU: Unhandled Table B mode %d (addr_in %08x PC %x)\n", tbmode, addr_in, REG_PC);
                break;

            case 1: // termination descriptor
                tbl_entry &= 0xffffff00;

                shift = is+abits+bbits+cbits;
                addr_out = ((addr_in<<shift)>>shift) + tbl_entry;
                resolved = 1;
                break;
        }
    }


//  fprintf(stderr,"PMMU: [%08x] => [%08x]\n", addr_in, addr_out);

    return addr_out;
}

/*

    m68881_mmu_ops: COP 0 MMU opcode handling

*/

void m68881_mmu_ops(void)
{
    uint16 modes;
    uint32 ea = m68ki_cpu.ir & 0x3f;
    uint64 temp64;

    // catch the 2 "weird" encodings up front (PBcc)
    if ((m68ki_cpu.ir & 0xffc0) == 0xf0c0)
    {
        fprintf(stderr,"680x0: unhandled PBcc\n");
        return;
    }
    else if ((m68ki_cpu.ir & 0xffc0) == 0xf080)
    {
        fprintf(stderr,"680x0: unhandled PBcc\n");
        return;
    }
    else    // the rest are 1111000xxxXXXXXX where xxx is the instruction family
    {
        switch ((m68ki_cpu.ir>>9) & 0x7)
        {
            case 0:
                modes = OPER_I_16();

                if ((modes & 0xfde0) == 0x2000) // PLOAD
                {
                    fprintf(stderr,"680x0: unhandled PLOAD\n");
                    return;
                }
                else if ((modes & 0xe200) == 0x2000)    // PFLUSH
                {
                    fprintf(stderr,"680x0: unhandled PFLUSH PC=%x\n", REG_PC);
                    return;
                }
                else if (modes == 0xa000)   // PFLUSHR
                {
                    fprintf(stderr,"680x0: unhandled PFLUSHR\n");
                    return;
                }
                else if (modes == 0x2800)   // PVALID (FORMAT 1)
                {
                    fprintf(stderr,"680x0: unhandled PVALID1\n");
                    return;
                }
                else if ((modes & 0xfff8) == 0x2c00)    // PVALID (FORMAT 2)
                {
                    fprintf(stderr,"680x0: unhandled PVALID2\n");
                    return;
                }
                else if ((modes & 0xe000) == 0x8000)    // PTEST
                {
                    fprintf(stderr,"680x0: unhandled PTEST\n");
                    return;
                }
                else
                {
                    switch ((modes>>13) & 0x7)
                    {
                        case 0: // MC68030/040 form with FD bit
                        case 2: // MC68881 form, FD never set
                            if (modes & 0x200)
                            {
                                switch ((modes>>10) & 7)
                                {
                                    case 0: // translation control register
                                        WRITE_EA_32(ea, m68ki_cpu.mmu_tc);
                                        break;

                                    case 2: // supervisor root pointer
                                        WRITE_EA_64(ea, (uint64)m68ki_cpu.mmu_srp_limit<<32 | (uint64)m68ki_cpu.mmu_srp_aptr);
                                        break;

                                    case 3: // CPU root pointer
                                        WRITE_EA_64(ea, (uint64)m68ki_cpu.mmu_crp_limit<<32 | (uint64)m68ki_cpu.mmu_crp_aptr);
                                        break;

                                    default:
                                        fprintf(stderr,"680x0: PMOVE from unknown MMU register %x, PC %x\n", (modes>>10) & 7, REG_PC);
                                        break;
                                }
                            }
                            else
                            {
                                switch ((modes>>10) & 7)
                                {
                                    case 0: // translation control register
                                        m68ki_cpu.mmu_tc = READ_EA_32(ea);

                                        if (m68ki_cpu.mmu_tc & 0x80000000)
                                        {
                                            m68ki_cpu.pmmu_enabled = 1;
                                        }
                                        else
                                        {
                                            m68ki_cpu.pmmu_enabled = 0;
                                        }
                                        break;

                                    case 2: // supervisor root pointer
                                        temp64 = READ_EA_64(ea);
                                        m68ki_cpu.mmu_srp_limit = (temp64>>32) & 0xffffffff;
                                        m68ki_cpu.mmu_srp_aptr = temp64 & 0xffffffff;
                                        break;

                                    case 3: // CPU root pointer
                                        temp64 = READ_EA_64(ea);
                                        m68ki_cpu.mmu_crp_limit = (temp64>>32) & 0xffffffff;
                                        m68ki_cpu.mmu_crp_aptr = temp64 & 0xffffffff;
                                        break;

                                    default:
                                        fprintf(stderr,"680x0: PMOVE to unknown MMU register %x, PC %x\n", (modes>>10) & 7, REG_PC);
                                        break;
                                }
                            }
                            break;

                        case 3: // MC68030 to/from status reg
                            if (modes & 0x200)
                            {
                                WRITE_EA_32(ea, m68ki_cpu.mmu_sr);
                            }
                            else
                            {
                                m68ki_cpu.mmu_sr = READ_EA_32(ea);
                            }
                            break;

                        default:
                            fprintf(stderr,"680x0: unknown PMOVE mode %x (modes %04x) (PC %x)\n", (modes>>13) & 0x7, modes, REG_PC);
                            break;
                    }
                }
                break;

            default:
                fprintf(stderr,"680x0: unknown PMMU instruction group %d\n", (m68ki_cpu.ir>>9) & 0x7);
                break;
        }
    }
}

